(use-modules (gnu packages)
             (guix packages)
             (ice-9 match)
             (srfi srfi-1)
             (srfi srfi-26))

(define* (package-all-inputs package #:key transitive?)
  (filter
   package?                             ;eliminate origin objects
   (map (match-lambda
          ((name package output ...)
           package))
        (delete-duplicates
         (remove null?
                 (if transitive?
                     (append (package-transitive-inputs package)
                             (package-transitive-native-inputs package)
                             (package-transitive-propagated-inputs package))
                     (append (package-inputs package)
                             (package-native-inputs package)
                             (package-propagated-inputs package))))))))

(define* (common-inputs packages #:key transitive?)
  (apply lset-intersection eq?
         (map (cut package-all-inputs <> #:transitive? transitive?)
              packages)))

(define* (common-inputs-count packages #:key transitive?)
  (let* ((package-sets (map (cut package-all-inputs <> #:transitive? transitive?)
                            packages))
         (packages (concatenate package-sets))
         (unique-packages (delete-duplicates packages)))
    (sort (fold (lambda (p result)
                  (cons (cons p (count (cut eq? p <>) packages))
                        result))
                '()
                unique-packages)
          (lambda (x y)
            (> (cdr x) (cdr y))))))

(define* (pp-inputs-count inputs-count #:key (max-count 20))
  (let* ((inputs-count* (take inputs-count max-count))
         (names (map package-name (map car inputs-count*)))
         (min-width (apply max (map (lambda (name)
                                      (string-length name))
                                    names)))
         (fmt (format #f "~~~da\t~~d~%" min-width)))
    (for-each (match-lambda
                ((package . count)
                 (format #t fmt (package-name package) count)))
              inputs-count*)))

;;; Example
;;;
;;; Try to find which common input package is most likely to affect a group of
;;; failing packages.
;;;
;;; The Guix build output was:
;; build of /gnu/store/qrwzykhz6n34vazfww95cxf38b1m11p6-weasyprint-51.drv
;; failed View build log at
;; '/var/log/guix/drvs/qr/wzykhz6n34vazfww95cxf38b1m11p6-weasyprint-51.drv.bz2'.
;; guix build: error: build of
;; `/gnu/store/0aqwliclll4yyl9y689fcz7lp2cks9w5-python-matplotlib-documentation-3.1.2.drv',
;; `/gnu/store/4l3f8karc2xya4lkpajv2c25qaah933q-freecad-0.18.4.drv',
;; `/gnu/store/5cvj1qh7v4vmhvgbzflm21d0g7pvvkhh-python-ipython-documentation-7.9.0.drv',
;; `/gnu/store/bgmci6ahcizr4dypv85hal603kaj8wy4-pplacer-1.1.alpha19.drv',
;; `/gnu/store/dn8rfa0zwg8n2mkvg7sa9zminzsq2llr-clipper-2.0.drv',
;; `/gnu/store/j7ipa3fh4ybvprgznc5i9nb3lq8y1ahb-python-numpy-documentation-1.17.3.drv',
;; `/gnu/store/lfv227c76rwnblg18if00db4j81qmdfj-python-faiss-1.5.0.drv',
;; `/gnu/store/qrwzykhz6n34vazfww95cxf38b1m11p6-weasyprint-51.drv',
;; `/gnu/store/zh4mcz5c9bggvnpx3nl6k2n8im3fdzkg-python-pari-jupyter-1.3.2.drv'
;; failed

(define (try-example)
  (let ((packages (map specification->package
                       '("python-matplotlib-documentation"
                         "python-ipython-documentation"
                         "clipper"
                         "python-numpy-documentation"
                         "python-faiss"
                         "python-pari-jupyter"))))

    (format #t "Common transitive inputs: ~a~%~%"
            (map package-name (common-inputs packages #:transitive? #t)))

    (format #t "Common inputs count:~%")
    (pp-inputs-count (common-inputs-count packages))))

;; =>

;; Common transitive inputs: (python-six zlib)

;; Common inputs count:
;; python-matplotlib	5
;; python-numpy     	4
;; pkg-config       	3
;; texinfo          	3
;; python-ipykernel 	3
;; python-numpydoc  	3
;; python-sphinx    	3
;; python-cython    	2
;; python-pytest    	2
;; python-mock      	2
;; python-ipython   	2
;; readline         	1
;; pari-gp          	1
;; python-tinycss2  	1
;; python-pyphen    	1
;; python-html5lib  	1
;; python-cssselect2	1
;; python-cffi      	1
;; python-cairosvg  	1
;; python-cairocffi 	1

;;; This suggests python-matplotlib or python-numpy might have something to do
;;; with it, and that 3 or 4 of these failures are likely unrelated.
